Add cheat code support (retro_cheat_set / retro_cheat_reset)#113
Add cheat code support (retro_cheat_set / retro_cheat_reset)#113JoeMatt wants to merge 1 commit intolibretro:masterfrom
Conversation
Wires up the previously-stubbed libretro cheat hooks so RetroArch users
can apply cheat codes to Jaguar games. The parser accepts the common
Pro Action Replay / GameShark-style hex format (6- or 8-digit address
followed by a 2-, 4-, or 8-digit byte/word/long value) with optional
space / colon / hyphen / dot separators, and supports multi-code strings
joined with '+' or newlines. Cheats are re-applied after every frame so
games that continuously overwrite the patched location are held to the
cheat value.
The parser and list management are extracted into src/cheat.{c,h} so
they are unit-testable without the full emulator. test/test_cheat.c
contains 130 assertions covering every accepted format length, all
separator styles, rejection of malformed input, list add/remove/toggle,
multi-code strings, capacity clamping, byte/word/long memory
application against a simulated big-endian address space, a frame-loop
scenario simulating an infinite-lives cheat overriding a "CPU" that
rewrites the value each frame, and real-world-shaped PAR examples.
The tests are plain C99, link only src/cheat.c (no BIOS or ROM
required), and are run in CI across every matrix target (Linux GCC,
Linux Clang, Linux aarch64, macOS arm64, Windows MSYS2) so cheat
support is verified on every supported platform.
There was a problem hiding this comment.
Pull request overview
Adds a self-contained cheat-code engine and wires it into the libretro core so RetroArch can enable/disable cheats and have them re-applied every frame (to override games that continuously rewrite patched memory).
Changes:
- Introduces
src/cheat.{c,h}implementing cheat parsing, list management, and callback-based application. - Integrates libretro
retro_cheat_set/retro_cheat_resetand applies cheats after eachJaguarExecuteNew()frame. - Adds C99 unit tests plus build/CI plumbing to compile and run them.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
test/test_cheat.c |
New self-contained unit tests covering parsing, list ops, and application via a simulated memory bus. |
src/cheat.h |
Public API/types for cheat parsing, list management, and callback-based memory writes. |
src/cheat.c |
Implementation of parser, list add/remove/toggle behavior, and apply loop. |
libretro.c |
Hooks libretro cheat callbacks into the new cheat engine and applies cheats every frame. |
Makefile.common |
Adds src/cheat.c to the core build sources. |
Makefile |
Adds make test target and cleans the built cheat test binary. |
.gitignore |
Ignores the cheat unit test binary under test/. |
.github/workflows/c-cpp.yml |
Runs the cheat unit tests in CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -o test_cheat test/test_cheat.c src/cheat.c | ||
| ./test_cheat |
There was a problem hiding this comment.
This CI step builds the test binary as ./test_cheat in the repo root, while the Makefile target builds test/test_cheat (and .gitignore ignores that path). To keep things consistent and avoid stray artifacts, consider invoking make test here, or at least outputting to test/test_cheat and running that.
| -o test_cheat test/test_cheat.c src/cheat.c | |
| ./test_cheat | |
| -o test/test_cheat test/test_cheat.c src/cheat.c | |
| ./test/test_cheat |
| if (!list || !code) | ||
| return; | ||
|
|
||
| cheat_list_remove_index(list, index); | ||
| if (!enabled) |
There was a problem hiding this comment.
cheat_list_set()'s header contract says that when enabled=false it should remove existing entries for index, but the implementation returns early when code == NULL before calling cheat_list_remove_index(). This means callers that pass NULL (or disable without a code string) will be unable to turn cheats off. Consider removing the !code early-return (or moving the NULL check after the enabled branch) so removal always happens regardless of code.
| if (!list || !code) | |
| return; | |
| cheat_list_remove_index(list, index); | |
| if (!enabled) | |
| if (!list) | |
| return; | |
| cheat_list_remove_index(list, index); | |
| if (!enabled || !code) |
| for (; *code && n < sizeof(buf) - 1; code++) | ||
| { | ||
| if (hex_digit((char)*code) >= 0) | ||
| buf[n++] = *code; | ||
| else if (*code != ' ' && *code != '\t' && *code != ':' && | ||
| *code != '-' && *code != '.') | ||
| return false; | ||
| } | ||
| buf[n] = '\0'; |
There was a problem hiding this comment.
cheat_parse_one() silently truncates input once it has collected 31 hex digits (buf size limit) without checking whether the original string still has additional hex digits. That can cause overlong inputs to be accepted after truncation (e.g., the first 16 digits form a valid code but extra digits follow). Consider detecting overflow (if the loop exits because n hit the limit and there are still characters left) and returning false, or cap the number of hex digits at 16 and reject anything beyond.
| * | ||
| * Build & run (from repo root): | ||
| * cc -O2 -Wall -std=c99 -I src -I libretro-common/include \ | ||
| * -o test_cheat test/test_cheat.c src/cheat.c && ./test_cheat |
There was a problem hiding this comment.
The build/run command in the file header produces an output binary named test_cheat in the repo root, but the Makefile/CI use test/test_cheat and .gitignore only ignores test/test_cheat. Following these instructions will leave an unignored binary in the root directory. Consider updating the example command to match the make test target (output to test/test_cheat and run that).
| * -o test_cheat test/test_cheat.c src/cheat.c && ./test_cheat | |
| * -o test/test_cheat test/test_cheat.c src/cheat.c && ./test/test_cheat |
Wires up the previously-stubbed libretro cheat hooks so RetroArch users can apply cheat codes to Jaguar games. The parser accepts the common Pro Action Replay / GameShark-style hex format (6- or 8-digit address followed by a 2-, 4-, or 8-digit byte/word/long value) with optional space / colon / hyphen / dot separators, and supports multi-code strings joined with '+' or newlines. Cheats are re-applied after every frame so games that continuously overwrite the patched location are held to the cheat value.
The parser and list management are extracted into src/cheat.{c,h} so they are unit-testable without the full emulator. test/test_cheat.c contains 130 assertions covering every accepted format length, all separator styles, rejection of malformed input, list add/remove/toggle, multi-code strings, capacity clamping, byte/word/long memory application against a simulated big-endian address space, a frame-loop scenario simulating an infinite-lives cheat overriding a "CPU" that rewrites the value each frame, and real-world-shaped PAR examples.
The tests are plain C99, link only src/cheat.c (no BIOS or ROM required), and are run in CI across every matrix target (Linux GCC, Linux Clang, Linux aarch64, macOS arm64, Windows MSYS2) so cheat support is verified on every supported platform.
This PR implements a complete cheat code engine for the Jaguar emulator, supporting the Pro Action Replay (PAR) / GameShark-style hex format commonly used for Jaguar game cheats.
Summary
Adds a self-contained, unit-tested cheat parsing and application system that integrates with the libretro frontend's cheat interface. The implementation is modular and testable in isolation from the main emulator.
Key Changes
New cheat engine (
src/cheat.c,src/cheat.h):cheat_parse_one(): Parses individual cheat codes in multiple formats (6+2, 6+4, 8+4, 6+8, 8+8 hex digit combinations)cheat_list_set(): Manages cheat entries with support for multi-code strings ('+' and newline-separated)cheat_list_apply(): Applies cheats via callback, enabling memory-agnostic operationComprehensive unit tests (
test/test_cheat.c):Integration with libretro core (
libretro.c):retro_cheat_reset()andretro_cheat_set()callbackscheat_apply_all()after CPU executionBuild system updates:
src/cheat.ctoMakefile.commonsource listmake testtarget to build and run unit tests.github/workflows/c-cpp.yml).gitignorefor test artifactsImplementation Details
The parser accepts codes like:
00003D00 FFFF(PAR canonical: 8-digit address + 4-digit word value)F03200 7F(6-digit address + 2-digit byte value)100000 CAFEBABE(6-digit address + 8-digit long value)Multi-code strings are supported for RetroArch
.chtfiles:The cheat application uses a callback pattern (
cheat_write_fn) to remain independent of the memory implementation, making the core logic fully unit-testable without the emulator.https://claude.ai/code/session_019diR7d7dZwUqdzB6J9TEic